`timescale 1ns / 1ps

// Simple Dual Port RAM
module zq_sdpram
#(
    parameter ADDR_WIDTH = 8,
    parameter DATA_WIDTH = 128,
    parameter DEPTH = 192,              // ADDR_WIDTH >= $clog2(DEPTH)
    parameter LATENCY = 2,              // read latency
    parameter RAMTYPE = "auto"          // auto, block, distributed, register
)
(
    input   clk,

    input   ena_w,
    input   wea,
    input   [ADDR_WIDTH-1: 0]    addr_w,
    input   [DATA_WIDTH-1: 0]    din,

    input   ena_r,
    input   [ADDR_WIDTH-1: 0]    addr_r,
    output  [DATA_WIDTH-1: 0]    dout
);

(* ram_style = RAMTYPE *)
reg  [DATA_WIDTH-1: 0]   ram [DEPTH-1: 0];

reg  [DATA_WIDTH-1: 0] o_reg [LATENCY-1: 0];

assign dout = o_reg[LATENCY-1];

always @(posedge clk)
begin
    if (ena_r)
        o_reg[0] <= ram[addr_r];
end

genvar i;
generate
    for (i = 0; i < LATENCY - 1; i = i + 1)
    begin
        always @(posedge clk)
        begin
            if (ena_r)
                o_reg[i + 1] <= o_reg[i];
        end
    end
endgenerate

always @(posedge clk)
begin
    if (ena_w & wea)
        ram[addr_w] <= din;
end

endmodule


// Asymmetric Simple Dual Port Block RAM
// Read > Write
// RATIO = 2, 4, 8, 16, 32
module zq_sdpram_asym_blk
#(
    parameter WADDR_WIDTH = 8,
    parameter WDATA_WIDTH = 32,
    parameter WDEPTH = 192,
    parameter RATIO  = 4,
    parameter LATENCY = 1,          // read latency
    parameter RAMTYPE = "block"     // auto, block, distributed, register
)
(
    input   clk,

    input   ena_w,
    input   [WADDR_WIDTH-1: 0]    addr_w,
    input   [WDATA_WIDTH-1: 0]    din,

    input   ena_r,
    input   [WADDR_WIDTH - $clog2(RATIO) -1: 0]     addr_r,
    output  [WDATA_WIDTH * RATIO         -1: 0]     dout
);

localparam CLOG2_RATIO = $clog2(RATIO);
localparam RADDR_WIDTH = WADDR_WIDTH - CLOG2_RATIO;
localparam RDATA_WIDTH = WDATA_WIDTH * RATIO;

(* ram_style = RAMTYPE *)
reg  [WDATA_WIDTH-1: 0]   ram [WDEPTH-1: 0];

reg  [RDATA_WIDTH-1: 0] o_reg [LATENCY-1: 0];

assign dout = o_reg[LATENCY-1];

integer lo;
always @(posedge clk)
begin
    if (ena_r)
    begin
        for (lo = 0; lo < RATIO; lo = lo + 1)
        begin
            o_reg[0][(lo+1) * WDATA_WIDTH -1 -: WDATA_WIDTH] <= ram[{ addr_r, lo[CLOG2_RATIO-1 : 0] }];
        end
    end
end

genvar i;
generate
    for (i = 0; i < LATENCY - 1; i = i + 1)
    begin
        always @(posedge clk)
        begin
            if (ena_r)
                o_reg[i + 1] <= o_reg[i];
        end
    end
endgenerate

always @(posedge clk)
begin
    if (ena_w)
        ram[addr_w] <= din;
end

endmodule


// Asymmetric Simple Dual Port RAM
// Read > Write
// RATIO = 2, 4, 8, 16, 32
module zq_sdpram_asym_lut
#(
    parameter WADDR_WIDTH = 8,
    parameter WDATA_WIDTH = 32,
    parameter WDEPTH = 192,
    parameter RATIO  = 4,
    parameter LATENCY = 1,              // read latency
    parameter RAMTYPE = "distributed"   // auto, block, distributed, register
)
(
    input   clk,

    input   ena_w,
    input   [WADDR_WIDTH-1: 0]    addr_w,
    input   [WDATA_WIDTH-1: 0]    din,

    input   ena_r,
    input   [WADDR_WIDTH - $clog2(RATIO) -1: 0]     addr_r,
    output  [WDATA_WIDTH * RATIO         -1: 0]     dout
);

localparam RDEPTH = WDEPTH / RATIO;
localparam CLOG2_RATIO = $clog2(RATIO);
localparam RADDR_WIDTH = WADDR_WIDTH - CLOG2_RATIO;
localparam RDATA_WIDTH = WDATA_WIDTH * RATIO;

wire [RADDR_WIDTH-1: 0] addr_hi;
wire [CLOG2_RATIO-1: 0] addr_lo;
wire [      RATIO-1: 0] wea;

assign {addr_hi, addr_lo} = addr_w;
assign wea = 1'b1 << addr_lo;

genvar i;
generate
    for (i = 0; i < RATIO; i = i + 1)
    begin
        zq_sdpram #(
            .ADDR_WIDTH ( RADDR_WIDTH ),
            .DATA_WIDTH ( WDATA_WIDTH ),
            .DEPTH      ( RDEPTH      ),
            .LATENCY    ( LATENCY     ),
            .RAMTYPE    ( RAMTYPE     )
        )
        inst_sdpram (
            .clk                     ( clk      ),

            .ena_w                   ( ena_w    ),
            .wea                     ( wea[i]   ),
            .addr_w                  ( addr_hi  ),

            .din                     ( din      ),
            .ena_r                   ( ena_r    ),
            .addr_r                  ( addr_r   ),
            .dout                    ( dout[i * WDATA_WIDTH + WDATA_WIDTH -1 : i * WDATA_WIDTH] )
        );
    end
endgenerate

endmodule


